﻿#include "ClientSocket.h"
#include "MyApp.h"
#include "unit.h"
#include "Msg.h"
#include "protocolstream.h"
#include "global.h"
#include "CIULog.h"

#include <QDebug>
#include <QHostAddress>
#include <QDataStream>
#include <QApplication>
#include <QString>
#include <QByteArray>
#include <QJsonValue>

const int ClientSocket::s_kMaxRetryDelayMs;
const int ClientSocket::s_kInitRetryDelayMs;

ClientSocket::ClientSocket(QObject *parent) :
    QObject(parent),
    m_pTcpSocket(nullptr),
    m_seq(0),
    m_isOkConnect(false),
    m_retryDelayMs(s_kInitRetryDelayMs)
{
    qRegisterMetaType<QJsonObject>("QJsonObject");
    qRegisterMetaType<QJsonDocument>("QJsonDocument");

    m_timer = new QTimer(this);
    m_timerRetry = new QTimer(this);
    m_pTcpSocket = new QTcpSocket(this);
    m_pTcpSocket->setSocketOption(QAbstractSocket::LowDelayOption,1);

    initConnection();
    m_timer->start(30000);
}

ClientSocket::~ClientSocket()
{
    delete m_pTcpSocket;    //need?
}

void ClientSocket::initConnection()
{
    connect(m_pTcpSocket,&QTcpSocket::connected,this,&ClientSocket::SltConnected);
    connect(m_pTcpSocket,&QTcpSocket::disconnected,this,&ClientSocket::SltDisconnected);
    connect(m_pTcpSocket,&QTcpSocket::readyRead,this,&ClientSocket::SltReadyRead);
    connect(m_timer,&QTimer::timeout,this,&ClientSocket::SltHeartbeat);
    connect(m_timerRetry,&QTimer::timeout,this,&ClientSocket::retryConnect);
    connect(m_pTcpSocket, static_cast<void(QTcpSocket::*)(QTcpSocket::SocketError)>(&QAbstractSocket::error),
          [=](QAbstractSocket::SocketError socketError){
        LOG_ERROR("DisConnect from host %s:%d , error : %s",
                  m_pTcpSocket->localAddress().toString().toStdString().c_str(),
                  m_pTcpSocket->localPort(),
                  m_pTcpSocket->errorString().toStdString().c_str());
        switch ( socketError ) {
        case QAbstractSocket::ConnectionRefusedError:   //连接被对方拒绝（或超时）
        case QAbstractSocket::SocketResourceError:      //本地系统超出资源（例如：太多的socket）
        case QAbstractSocket::RemoteHostClosedError:    //远程socket关闭了连接
            Q_EMIT signalStatus(DisConnectedHost);
            m_isOkConnect = false;
            if(!m_timerRetry->isActive())
            {
                m_timerRetry->start(m_retryDelayMs);
            }
            break;
        case QAbstractSocket::NetworkError:             //连接时发生错误(比如地址错误导致)
            Q_EMIT signalStatus(DisConnectedHost);
            break;
        case QAbstractSocket::SocketAccessError:        //socket操作失败，因为应用程序缺乏必要的特权
        case QAbstractSocket::UnknownSocketError:       //发生无法识别的错误
        case QAbstractSocket::HostNotFoundError:        //本地socket名字没有被发现
        case QAbstractSocket::SocketTimeoutError:       //socket操作超时
        case QAbstractSocket::DatagramTooLargeError:    //数据报比操作系统的限制（可低至8192字节）大。
        case QAbstractSocket::UnsupportedSocketOperationError:  //本地操作系统不支持请求的socket操作
        case QAbstractSocket::OperationError:           //当socket在一个不允许它的状态中时，试图进行操作
            break;
        default:
            break;
        }
    });
}

void ClientSocket::CheckConnected()
{
    if(m_pTcpSocket->state() != QTcpSocket::ConnectedState)
    {
        m_pTcpSocket->connectToHost(MyApp::m_strHostAddr,MyApp::m_nMsgPort);
    }
}

//主动断开连接,相当于linux下close()函数,disconnectFromHost()对应shutdown()
void ClientSocket::CloseConnected()
{
    if (m_pTcpSocket->isOpen()) m_pTcpSocket->abort();
}

//如果客户端与服务端掉线，自动重连
void ClientSocket::retryConnect()
{
    if(!m_isOkConnect)
    {
        qDebug() << "ClientSocket::retry " << m_retryDelayMs << " milliseconds. ";
        m_timerRetry->start(m_retryDelayMs);
        m_retryDelayMs = qMin(m_retryDelayMs * 2, s_kMaxRetryDelayMs);

        if (m_pTcpSocket->isOpen())
        {
            m_pTcpSocket->abort();
        }
        m_pTcpSocket->connectToHost(MyApp::m_strHostAddr,MyApp::m_nMsgPort);
    }
}

//连接服务器，用于客户端主动提供连接
void ClientSocket::ConnectToHost()
{
    LOG_INFO("");
    if (m_pTcpSocket->isOpen())
    {
        m_pTcpSocket->abort();
    }
    m_pTcpSocket->connectToHost(MyApp::m_strHostAddr,MyApp::m_nMsgPort);
}

void ClientSocket::SltSend(int cmd, const QJsonObject &json)
{
    if(!m_isOkConnect) return;
    if(cmd != msg_type_heartbeart)
    {
        m_timer->start(30000);
    }

    QJsonDocument data(json);
    //JsonFormat：Compact（不带格式StyledWriter），Indented（带格式FastWriter）
    QByteArray msgData = data.toJson(QJsonDocument::Compact);
    qDebug() << "ClientSocket::SltSend: " << json;
    std::string outbuf;
    yt::BinaryWriteStream3 writeStream(&outbuf);
    writeStream.Write( cmd );
    writeStream.Write( m_seq );
    writeStream.Write( msgData.constData(), static_cast<size_t>(msgData.size()) );
    writeStream.Flush();

    msg header = { static_cast<int32_t>(outbuf.length()) };
    outbuf.insert( 0, reinterpret_cast<const char*>(&header), sizeof(header) );
    SendPackage( outbuf.c_str(), outbuf.length() );
    //m_seq++;
}

void ClientSocket::SltSendtoTargetid(int cmd, const QJsonObject &json, int targetid)
{
    if(!m_isOkConnect) return;
    QJsonDocument data(json);
    QByteArray msgData = data.toJson(QJsonDocument::Compact);
    qDebug() << "json data: " << msgData;
    std::string outbuf;
    yt::BinaryWriteStream3 writeStream(&outbuf);
    writeStream.Write(cmd);
    writeStream.Write(m_seq);
    writeStream.Write(msgData.constData(), static_cast<size_t>(msgData.size()) );
    writeStream.Write(targetid);
    writeStream.Flush();

    msg header = { static_cast<int32_t>(outbuf.length()) };
    outbuf.insert(0, reinterpret_cast<const char*>(&header), sizeof(header));
    SendPackage(outbuf.c_str(), outbuf.length());
}

/**
 * @brief ClientSocket::SendPackage 发送数据包，加上协议头struct msg
 * @param str       包体
 * @param length    包体长度
 */
bool ClientSocket::SendPackage(const char* pBuffer, qint64 length)
{
    Q_ASSERT(pBuffer!=nullptr && length>0);

    int nSentBytes = 0;
    qint64 nRet = 0;
    do
    {
        nRet = m_pTcpSocket->write(pBuffer,length);
        if(nRet == -1)
        {
            //一旦出现错误就立刻关闭Socket
            LOG_ERROR("Send data error, disconnect server:%s, port:%d.",
                      MyApp::m_strFileServerAddr.toStdString().c_str(),
                      MyApp::m_nFileSeverPort);
            CloseConnected();
            return false;
        }

        nSentBytes += nRet;

        if(nSentBytes>=length)
            break;
        myHelper::Sleep(1000);
    } while (true);
    return true;
}

// 与服务器断开连接
void ClientSocket::SltDisconnected()
{
    LOG_INFO("DisConnect from host %s:%d",
              m_pTcpSocket->localAddress().toString().toStdString().c_str(),
              m_pTcpSocket->localPort());
    m_seq = 0;
    m_isOkConnect = false;
    m_timerRetry->start(m_retryDelayMs);
    Q_EMIT signalStatus(DisConnectedHost);
}

// 连接上服务器
void ClientSocket::SltConnected()
{
    LOG_INFO("Connect to host %s:%d",
              m_pTcpSocket->localAddress().toString().toStdString().c_str(),
              m_pTcpSocket->localPort());
    Q_EMIT signalStatus(ConnectedHost);
    m_isOkConnect = true;
    m_retryDelayMs = s_kInitRetryDelayMs;
    m_timerRetry->stop();
}

// tcp消息处理
void ClientSocket::SltReadyRead()
{
    //缓冲区没有数据，直接无视
    if( m_pTcpSocket->bytesAvailable() <= 0 )
    {
        return;
    }
    //临时获得从缓存区取出来的数据，但是不确定每次取出来的是多少。
    QByteArray buffer;
    //如果是信号readyRead触发的，使用readAll时会一次把这一次可用的数据全总读取出来
    //所以使用while(m_tcpClient->bytesAvailable())意义不大，其实只执行一次。
    buffer = m_pTcpSocket->readAll();
    m_buffer.append(buffer);

    while(true)
    {
        //不够一个包头大小
        if (m_buffer.size() < sizeof(msg))
        {
            break;
        }

        //不够一个整包大小
        msg header;
        memcpy(&header, m_buffer.constData(), sizeof(msg));
        if (m_buffer.size() < header.packagesize + sizeof(msg))
            break;

        //去除包头
        m_buffer = m_buffer.mid(sizeof(msg));

        if (!Process(m_buffer.constData(), header.packagesize))
        {
            qDebug() << "Process error, close TcpConnection";
            m_pTcpSocket->abort();
        }
        //去掉包体
        m_buffer = m_buffer.mid(header.packagesize);
    }
}

//发送心跳消息
void ClientSocket::SltHeartbeat()
{
    //先关闭，方便调试
    SltSend(msg_type_heartbeart,QJsonObject());
}

/**
 * @brief ClientSocket::Process 解析包体数据，完成消息分类
 * @param inbuf     包体首地址
 * @param length    包体长度
 * @return          成功:true
 */
bool ClientSocket::Process(const char* inbuf, size_t length)
{
    yt::BinaryReadStream2 readStream(inbuf, length);
    int cmd;
    if (!readStream.Read(cmd))
    {
        LOG_WARNING("read cmd error !!!");
        return false;
    }

    //int seq;
    if (!readStream.Read(m_seq))
    {
        LOG_WARNING("read seq error !!!");
        return false;
    }

    std::string data;
    size_t datalength;
    if (!readStream.Read(&data, 0, datalength))
    {
        LOG_WARNING("read data error !!!");
        return false;
    }
    if(cmd != msg_type_heartbeart)
    {
        LOG_INFO("Recv from client: cmd=%d, seq=%d, header.packagesize=%d, data=%s, datalength=",
                 cmd, m_seq, length, data.c_str(), datalength );
    }

    switch(cmd)
    {
    //心跳包
    case msg_type_heartbeart:
    {
        //OnHeartbeatResponse();
    }
        break;

    //注册
    case msg_type_register:
    {
        OnRegisterResult(data.c_str(), data.size());
    }
        break;

    //登录
    case msg_type_login:
    {
        OnLoginResult(data.c_str(),data.size());
    }
        break;

    //获取好友列表
    case msg_type_getofriendlist:
    {
        OnGotFriendList(data.c_str(),data.size());
    }
        break;

    //查找用户
    case msg_type_finduser:
    {
        OnFindUser(data.c_str(),data.size());
    }
        break;

    //加好友
    case msg_type_operatefriend:
    {
        OnOperateFriend(data.c_str(),data.size());
    }
        break;
    //更新用户状态
    case msg_type_userstatuschange:
    {
        int userid = 0;
        if (!readStream.Read(userid))
        {
            LOG_ERROR("read update userid error!!!");
            return false;
        }
        OnUpdateUserStatus(userid,data.c_str(),data.size());
    }
        break;
    //更新用户信息
    case msg_type_updateuserinfo:
    {
        OnUpdateUserInfo(data.c_str(),data.size());
    }
        break;

    //修改密码
    case msg_type_modifypassword:
    {}
        break;

    //创建群
    case msg_type_creategroup:
    {}
        break;

    //获取指定群成员信息
    case msg_type_getgroupmembers:
    {}
        break;

    //聊天消息
    case msg_type_chat:
    {
        int senderid = 0;
        int targetid = 0;
        if (!readStream.Read(senderid))
        {
            LOG_ERROR("read update userid error!!!");
            return false;
        }
        if (!readStream.Read(targetid))
        {
            LOG_ERROR("read update userid error!!!");
            return false;
        }
        OnChatMsg(data.c_str(),data.size(),senderid);
    }
        break;

    //群发消息
    case msg_type_multichat:
    {}
        break;

        default:
            LOG_INFO("unsupport cmd, cmd: %d",cmd);
            return false;
    }
    m_seq++;
    return true;
}

//注册结果
void ClientSocket::OnRegisterResult(const char* data, size_t length)
{
    QJsonObject jsonObj;
    ParseDataToJson(data,length,jsonObj);

    int code = jsonObj.value("code").toInt();
    switch(code)
    {
    case error_code_ok:
    {
        jsonObj.remove("code");
        jsonObj.remove("msg");
        Q_EMIT signalStatus(RegisterOk);
    }
        break;

    case error_code_registerfail:
        Q_EMIT signalStatus(RegisterFailed);
        break;

    case error_code_registeralready:
        Q_EMIT signalStatus(RegisterAlready);
        break;

    default:
        LOG_INFO( "unsupport code, code: %d,msg: %s.",
                  code,
                  jsonObj.value("msg").toString().toStdString().c_str() );
        break;
    }
}

//解析登录结果
void ClientSocket::OnLoginResult(const char *data, size_t length)
{
    QJsonObject jsonObj;
    ParseDataToJson(data,length,jsonObj);

    int code = jsonObj.value("code").toInt();
    switch(code)
    {
        case error_code_ok:
            jsonObj.remove("code");
            jsonObj.remove("msg");
            Q_EMIT sigUserInfo(jsonObj);
            Q_EMIT signalStatus(LoginSuccess);
        break;

        case error_code_notregister:
            Q_EMIT signalStatus(LoginNotRegister);
        break;

        case error_code_invalidpassword:
            Q_EMIT signalStatus(LoginPasswdError);
        break;

        default:
            LOG_INFO( "unsupport code, code: %d,msg: %s.",
                      code,
                      jsonObj.value("msg").toString().toStdString().c_str() );
        break;
    }
}

//更新当前登录用户个人信息
void ClientSocket::OnUpdateUserInfo(const char *data, size_t length)
{
    QJsonObject jsonObj;
    ParseDataToJson(data,length,jsonObj);

    int code = jsonObj.value("code").toInt();
    switch(code)
    {
        case error_code_ok:
            jsonObj.remove("code");
            jsonObj.remove("msg");
            Q_EMIT sigUserInfo(jsonObj);
        break;

        case error_code_updateuserinfofail:
            jsonObj.remove("code");
            jsonObj.remove("msg");
            Q_EMIT sigUserInfo(jsonObj);
        break;

        default:
            LOG_INFO( "unsupport code, code: %d,msg: %s.",
                      code,
                      jsonObj.value("msg").toString().toStdString().c_str() );
        break;
    }
}

//状态(在线、离线、个人资料)更新的用户 2018/10/12
void ClientSocket::OnUpdateUserStatus(int userid, const char *data, size_t length)
{
    QJsonObject jsonObj;
    ParseDataToJson(data,length,jsonObj);
    int type = jsonObj.value("type").toInt();
    switch (type) {
    case 1://在线
        Q_EMIT sigUpdateUserStatus(userid,STATUS_ONLINE);
        break;
    case 2://离线
        Q_EMIT sigUpdateUserStatus(userid,STATUS_OFFLINE);
        break;
    case 3://好友个人信息更改
        jsonObj.remove("type");
        Q_EMIT sigUpdateFriendInfo(jsonObj);
        //SltSend(msg_type_getofriendlist,QJsonObject());
        break;
    default:
        break;
    }
}

//获取好友列表 2018/10/12
void ClientSocket::OnGotFriendList(const char *data, size_t length)
{
    QJsonObject jsonObj;
    ParseDataToJson(data,length,jsonObj);

    int code = jsonObj.value("code").toInt();
    switch(code)
    {
        case error_code_ok:
            jsonObj.remove("code");
            jsonObj.remove("msg");
            Q_EMIT sigFriendList(jsonObj);
        break;

        default:
            LOG_INFO( "unsupport code, code: %d,msg: %s.",
                      code,
                      jsonObj.value("msg").toString().toStdString().c_str() );
        break;
    }
}

//查找好友
void ClientSocket::OnFindUser(const char *data, size_t length)
{
    QJsonObject jsonObj;
    ParseDataToJson(data,length,jsonObj);

    int code = jsonObj.value("code").toInt();
    switch(code)
    {
        case error_code_ok:
            jsonObj.remove("code");
            jsonObj.remove("msg");
            Q_EMIT sigFindUser(jsonObj);
        break;

        default:
            LOG_INFO( "unsupport code, code: %d,msg: %s.",
                      code,
                      jsonObj.value("msg").toString().toStdString().c_str() );
        break;
    }
}

//type为1发出加好友申请 2 收到加好友请求(仅客户端使用) 3应答加好友(发送/接收对方) 4删除好友请求（发送给对方） 5应答删除好友（） 6服务端回应已经是好友关系
//当type=3时，accept是必须字段，0对方拒绝，1对方接受
void ClientSocket::OnOperateFriend(const char *data, size_t length)
{
    QJsonObject jsonObj;
    ParseDataToJson(data,length,jsonObj);

    int type = jsonObj.value("type").toInt();
    jsonObj.remove("type");
    switch (type) {
    case 2:
        Q_EMIT sigRequestAddUser(jsonObj);
        break;
    case 3:
        //int select = jsonObj.value("accept").toInt();
        if(jsonObj.value("accept").toInt() == 1)
        {   //添加好友成功，更新好友列表
            Q_EMIT sigAddUserResult(jsonObj.value("username").toString(),1);
            SltSend(msg_type_getofriendlist,QJsonObject());
        }else
        {
            //添加好友成功/失败都需要发信息告诉界面
            Q_EMIT sigAddUserResult(jsonObj.value("username").toString(),0);
        }
        break;
    case 5://好友删除，更新好友列表
        Q_EMIT sigDelFriend(jsonObj.value("userid").toInt());
        SltSend(msg_type_getofriendlist,QJsonObject());
        break;
    case 6://已经好友关系，重新拉取该好友信息
        SltSend(msg_type_getofriendlist,QJsonObject());
        Q_EMIT sigReplyUserExisted(jsonObj.value("userid").toInt(),jsonObj.value("username").toString());
        break;
    default:
        break;
    }
}

void ClientSocket::OnChatMsg(const char *data, size_t length, int senderid)
{
    QJsonObject jsonObj;
    ParseDataToJson(data,length,jsonObj);

    int type = jsonObj.value("msgType").toInt();
    jsonObj.remove("msgType");
    jsonObj.remove("clientType");
    switch (type) {
    case 1:
        Q_EMIT sigChatMsg(jsonObj,senderid);
        break;
    default:
        break;
    }
}

//将字符串data解析成json 2018/10/12
void ClientSocket::ParseDataToJson(const char *data, size_t length, QJsonObject& jsonObj)
{
    QByteArray jsonData(data,length);
    QJsonParseError jsonError;
    // 转化为 JSON 文档
    QJsonDocument document = QJsonDocument::fromJson(jsonData,&jsonError);
    // 解析未发生错误
    if(!document.isNull() && (jsonError.error == QJsonParseError::NoError))
    {
        // JSON 文档为对象
        if (document.isObject())
        {
            // 转化为对象
            jsonObj = document.object();
        }
    }
}
